from abc import ABC, abstractmethod
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
from typing import Literal, Union

UNITS_NAME = Literal['pt', 'inch', 'fs', 'px', 'relx', 'rely', 'rel']
"""单位名称类型"""

def i2p(inch: float) -> float:
    """英寸转点"""
    return inch * 72

def p2i(pt: float) -> float:
    """点转英寸"""
    return pt / 72

class Length(ABC):
    """长度单位转换基本类型"""

    length_unit = True

    def __init__(self, value: float, fig: Figure = None, unit: str = None, 
                 dir: Literal['x', 'y']=None):
        """以 inch 为基本的长度单位"""
        self._value = value
        self._fig = fig
        self._unit_name = unit
        self._font_size = plt.rcParams['font.size']
        self._dir = dir

    @abstractmethod
    def to_inch(self) -> float:
        """转换为 inch"""
        pass

    def to_pt(self) -> float:
        """转换为 pt"""
        return i2p(self.to_inch())

    def to_fs(self) -> float:
        """转换为 字体大小相对单位"""
        return self.to_pt() / self._font_size

    def to_px(self) -> float:
        """转换为 px"""
        return self.to_inch() * self._fig.dpi

    def to_rel(self, dir:Literal['x', 'y']=None, fig: Figure=None) -> float:
        """转换为 Figure 相对单位"""
        if fig is not None:
            self._fig = fig

        if self._fig is None:
            raise ValueError("Figure is not set")

        if dir is not None:
            self._dir = dir

        if self._dir == 'x':
            return self.to_inch() / self._fig.get_size_inches()[0]
        else:
            return self.to_inch() / self._fig.get_size_inches()[1]

    def to_relx(self, fig: Figure=None) -> float:
        """转换为 Figure 水平相对单位"""
        return self.to_rel('x', fig)

    def to_rely(self, fig: Figure=None) -> float:
        """转换为 Figure 垂直相对单位"""
        return self.to_rel('y', fig)

    def to(self, unit: UNITS_NAME, fig: Figure=None, 
           dir: Literal['x', 'y']=None) -> float:
        """转换为指定单位"""
        if fig is not None:
            self._fig = fig
        if dir is not None:
            self._dir = dir
            
        if unit == 'pt':
            return self.to_pt()
        elif unit == 'inch':
            return self.to_inch()
        elif unit == 'fs':
            return self.to_fs()
        elif unit == 'px':
            return self.to_px()
        elif unit == 'relx':
            return self.to_relx()
        elif unit == 'rely':
            return self.to_rely()
        elif unit == 'rel':
            return self.to_rel()
        else:
            raise ValueError("Invalid unit type")

    def __add__(self, other: Union[int, float, 'Length']) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(self.to_inch() + other, self._fig, self._dir)
        elif isinstance(other, Length):
            return Inch(self.to_inch() + other.to_inch(), self._fig, self._dir)
        return NotImplemented

    def __radd__(self, other: Union[int, float, 'Length']) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(other + self.to_inch(), self._fig, self._dir)
        elif isinstance(other, Length):
            return Inch(other.to_inch() + self.to_inch(), self._fig, self._dir)
        return NotImplemented

    def __sub__(self, other: Union[int, float, 'Length']) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(self.to_inch() - other, self._fig, self._dir)
        elif isinstance(other, Length):
            return Inch(self.to_inch() - other.to_inch(), self._fig, self._dir)
        return NotImplemented

    def __rsub__(self, other: Union[int, float, 'Length']) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(other - self.to_inch(), self._fig, self._dir)
        elif isinstance(other, Length):
            return Inch(other.to_inch() - self.to_inch(), self._fig, self._dir)
        return NotImplemented

    def __mul__(self, other: int | float) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(self.to_inch() * other, self._fig, self._dir)
        return NotImplemented

    def __rmul__(self, other: int | float) -> 'Length':
        if isinstance(other, (int, float)):
            return Inch(other * self.to_inch(), self._fig, self._dir)
        return NotImplemented

    def __truediv__(self, other: Union[float,int, 'Length']) -> 'Length':
        if isinstance(other, Length):
            # 两个 Length 对象相除，返回一个数字，变为无量纲
            return self.to_inch() / other.to_inch()
        elif isinstance(other, (int, float)):
            return Inch(self.to_inch() / other, self._fig, self._dir)
        return NotImplemented

    def __repr__(self):
        return f"<Length: {self._value} {self._unit_name}>"

    def update_fig(self, fig: Figure):
        self._fig = fig


class Pt(Length):
    """点单位"""

    def __init__(self, value: float, fig: Figure=None,
                 dir: Literal['x', 'y']=None):
        super().__init__(value, fig, 'pt', dir)

    def to_inch(self) -> float:
        return self._value / 72

    def to_pt(self) -> float:
        return self._value


class Inch(Length):
    """英寸单位"""

    def __init__(self, value: float, fig: Figure=None,
                 dir: Literal['x', 'y']=None):
        super().__init__(value, fig, 'inch', dir)

    def to_inch(self) -> float:
        return self._value


class Fs(Length):
    """字体大小相对单位，1=font_size"""

    def __init__(self, value: float, fig: Figure=None,
                 dir: Literal['x', 'y']=None):
        super().__init__(value, fig, 'fs', dir)

    def to_inch(self) -> float:
        return p2i(self._value * self._font_size)

    def to_fs(self) -> float:
        return self._value


class Px(Length):
    """像素单位"""

    def __init__(self, value: float, fig: Figure=None,
                 dir: Literal['x', 'y']=None):
        super().__init__(value, fig, 'px', dir)

    def to_inch(self) -> float:
        return self._value / self._fig.dpi

    def to_px(self) -> float:
        return self._value


class RelX(Length):
    """Figure 水平相对单位 0~1"""

    def __init__(self, value: float, fig: Figure=None,):
        super().__init__(value, fig, 'relx', 'x')

    def to_inch(self) -> float:
        return self._value * self._fig.get_size_inches()[0]

    def to_relx(self, fig: Figure=None):
        if fig is not None:
            self._fig = fig
        return self._value


class RelY(Length):
    """Figure 垂直相对单位 0~1"""

    def __init__(self, value: float, fig: Figure=None):
        super().__init__(value, fig, 'rely', 'y')

    def to_inch(self) -> float:
        return self._value * self._fig.get_size_inches()[1]

    def to_rely(self, fig: Figure=None):
        if fig is not None:
            self._fig = fig
        return self._value


class Rel(Length):
    """Figure 相对单位 0~1，需要指定方向"""

    def __init__(self, value: float, fig: Figure=None, 
                 dir: Literal['x', 'y']='x'):
        super().__init__(value, fig, 'rely', dir)

    def to_inch(self) -> float:
        if self._dir == 'x':
            return self._value * self._fig.get_size_inches()[0]
        else:
            return self._value * self._fig.get_size_inches()[1]




class LengthUnitFactory:
    """长度单位工厂"""
    def __init__(self, fig: Figure, unit: UNITS_NAME):
        self.fig = fig
        self.unit = unit

    def _create(self, value: float, dir: Literal['x', 'y']) -> Length:
        if self.unit == 'pt':
            return Pt(value, self.fig)
        elif self.unit == 'inch':
            return Inch(value, self.fig)
        elif self.unit == 'fs':
            return Fs(value, self.fig)
        elif self.unit == 'px':
            return Px(value, self.fig)
        elif self.unit == 'relx':
            return RelX(value, self.fig)
        elif self.unit == 'rely':
            return RelY(value, self.fig)
        elif self.unit == 'rel':
            return Rel(value, self.fig, dir)
        else:
            raise ValueError("Invalid unit type")

    def __call__(self, value: float, dir: Literal['x', 'y']=None) -> Length:
        return self._create(value, dir)

LenType = float | int | Length
"""长度类型"""

def as_unit_value(unit: UNITS_NAME, *args: Length|float, fig:Figure=None,
                  dir: Literal['x', 'y']=None) -> tuple[float] | float:
    """将传入的参数转换为指定单位的值，如果是 Length 类型，则转换为指定单位的值，否则返回原值"""
    result = []
    for arg in args:
        if isinstance(arg, Length):
            if fig is not None:
                arg.update_fig(fig)
            result.append(arg.to(unit, fig, dir))
        else:
            result.append(arg)
    if len(result) == 1:
        return result[0]
    return tuple(result)


def as_inch(fig: Figure, *args: Length|float) -> tuple[Length] | Length:
    """用于处理向 Matplotlib 传入长度时的兼容，确保长度是一个 Length 对象。
    如果是一个数字，则认为是英寸；如果是 Length 类型，则直接返回。
    可以传入多个参数，返回的是一个 tuple[Length]
    """
    result = []
    for arg in args:
        if isinstance(arg, Length):
            arg.update_fig(fig)
            result.append(arg)
        else:
            result.append(Inch(arg, fig))
    if len(result) == 1:
        return result[0]
    return tuple(result)


def as_inch_value(fig: Figure, *args: Length|float) -> tuple[float] | float:
    """将传入的参数转换为英寸值"""
    result = []
    for arg in args:
        if isinstance(arg, Length):
            arg.update_fig(fig)
            result.append(arg.to_inch())
        else:
            result.append(arg)
    if len(result) == 1:
        return result[0]
    return tuple(result)